#include "TIMRegister.h"
#include "common.h"
#include <stdint.h>

// 当前允许最大频率设置为1MHz
static const uint32_t TIMMaxFreq = TIM_DEFAULT_CLK;

/**
 * @brief  An easier interface to configure and register a TIM peripheral with
 * some specified parameters.
 * @param  timAddr: Base address of the TIM peripheral to configure.
 *                  Supports TIM2 to TIM7.
 * @param  freq: Desired frequency for the TIM peripheral.
 *               Capped at maxFreq (1MHz).
 * @param  counterMode: Counter mode for the TIM peripheral. Either
 * TIM_CounterMode_Up or TIM_CounterMode_Down.
 * @param  priority: Preemption priority for the interrupt. Lower, more urgent.
 * @param  subPriority: Sub priority for the interrupt.
 * @retval None
 *
 * @note   This function enables the clock for the specified TIM peripheral,
 * configures the time base unit with the given frequency, and enables the
 * update interrupt. It also sets up the NVIC interrupt with the specified
 * priority and subpriority. If an unsupported TIM peripheral is passed, the
 * function will trigger an assert. Remember to write your TIM interrupt
 * handler in stm32f10x_it.c.
 */
void TIM_Register(TIM_TypeDef *timAddr, uint32_t freq, uint16_t counterMode,
                  int priority, int subPriority) {
  uint32_t timPort = 0;
  IRQn_Type timInt = 0;
  // temporarily only support TIM2~TIM7
  switch ((uint32_t)timAddr) {
  case (uint32_t)TIM2:
    timPort = RCC_APB1Periph_TIM2;
    timInt = TIM2_IRQn;
    break;
  case (uint32_t)TIM3:
    timPort = RCC_APB1Periph_TIM3;
    timInt = TIM3_IRQn;
    break;
  case (uint32_t)TIM4:
    timPort = RCC_APB1Periph_TIM4;
    timInt = TIM4_IRQn;
    break;
  case (uint32_t)TIM5:
    timPort = RCC_APB1Periph_TIM5;
    timInt = TIM5_IRQn;
    break;
  case (uint32_t)TIM6:
    timPort = RCC_APB1Periph_TIM6;
    timInt = TIM6_IRQn;
    break;
  case (uint32_t)TIM7:
    timPort = RCC_APB1Periph_TIM7;
    timInt = TIM7_IRQn;
    break;
  default:
    // unsupported
    assert_param(0);
    break;
  }
  freq = min(freq, TIMMaxFreq);

  // common TIM parameter configuration begins here
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  RCC_APB1PeriphClockCmd(timPort, ENABLE); // TIM clock enable
  // Time base configuration
  TIM_TimeBaseStructure.TIM_Prescaler = (TIM_DEFAULT_SCALE - 1);
  uint16_t counterPeriod = TIMMaxFreq / freq;
  TIM_TimeBaseStructure.TIM_Period = (counterPeriod - 1);
  TIM_TimeBaseStructure.TIM_ClockDivision = 0; // no clock division
  TIM_TimeBaseStructure.TIM_CounterMode = counterMode;
  TIM_TimeBaseInit(timAddr, &TIM_TimeBaseStructure);

  // Interrupt Parameter Configuration
  NVIC_InitTypeDef NVIC_InitStructure;
  /* Set the Vector Table base address at 0x08000000 */
  // NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0000);

  NVIC_PriorityGroupConfig(
      NVIC_PriorityGroup_1); // may interrupted by the most important
  NVIC_InitStructure.NVIC_IRQChannel = timInt; //  interrupt triggered by TIM
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = priority;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = subPriority;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  TIM_ClearITPendingBit(timAddr,
                        TIM_IT_Update);         // Clear TIM update pending flag
  TIM_ITConfig(timAddr, TIM_IT_Update, ENABLE); // TIM IT enable
  TIM_Cmd(timAddr, ENABLE);                     // TIM5 enable counter
}

/**
 * @brief  Gets the TIMx auto reload value. The Setter is TIM_SetAutoreload(),
 * provided by stm32f10x_tim.c
 * @param  TIMx: where x can be 1 to 17 to select the TIM peripheral.
 * @retval The TIMx auto reload value.
 */
uint16_t TIM_GetAutoreload(TIM_TypeDef *timAddr) {
  assert_param(IS_TIM_ALL_PERIPH(timAddr));
  return (timAddr->ARR + 1);
}

#ifdef DOWNCOUNTER_MODULE_ENABLED
static uint32_t currCount = 0, prevCount = 0, scaler = 1, innerTick = 0;
static int32_t TickOn = 0, timeSUp = 0;
void DownCounter_Config(TIM_TypeDef *timAddr) {
  scaler = 1000 / TIM_DEFAULT_PERIOD_us / TIM_GetAutoreload(timAddr);
}

void DownCounter_Set(uint32_t ms) {
  currCount = ms;
  TickOn = 1;
  timeSUp = 0;
  innerTick = 0;
}
void DownCounter_Tick(void) {
  if (TickOn) {
    innerTick++;
    if (innerTick >= scaler) {
      innerTick = 0;
      prevCount = currCount;
      if (currCount == 0) {
        TickOn = 0;
        timeSUp = 1;
      } else {
        currCount--;
      }
    }
  }
}
int TimeIsUp(void) { return timeSUp; }

void DownCounter_Reset(void) {
  currCount = prevCount = 0;
  TickOn = 0;
  timeSUp = 0;
  innerTick = 0;
}
#endif